package domain.searchEngine.compass;

import java.text.SimpleDateFormat ;
import java.util.Calendar ;
import java.util.Date ;
import java.util.HashSet ;
import java.util.Set ;

import org.compass.core.CompassHit ;
import org.compass.core.CompassHits ;
import org.compass.core.CompassQuery ;
import org.compass.core.CompassQueryBuilder ;
import org.compass.core.CompassSession ;
import org.compass.core.CompassTransaction ;

import util.DateTools ;
import util.Log ;
import util.NotFoundException ;
import util.Statistics ;
import util.Subsystem ;
import domain.ForumData ;
import domain.message.Post ;
import domain.searchEngine.SearchEngine ;
import domain.searchEngine.SearchResult ;
import domain.user.Login ;
import domain.user.Member ;

/**
 * search engine interface to Compass.
 *
 * @author hadas, reut
 */
public class CompassSearchEngine implements SearchEngine
{
	private CompassSession compassSession;
	private ForumData forum ;

	/**
	 * @param session session for the search engine to execute its queries on.
	 * @param forum where to obtain domain objects from when searching by domain objects.
	 */
	public CompassSearchEngine(final CompassSession session, final ForumData forum)
	{
		this.compassSession = session;
		this.forum = forum ;
	}
	/**
	 * @see domain.searchEngine.SearchEngine#close()
	 */
	@Override
	public void close()
	{
		this.compassSession.close() ;
	}

	/**
	 * @return a per-hour post authoring statistics.
	 */
	public Statistics getPostStatistics()
	{
		CompassTransaction transaction =  this.compassSession.beginTransaction() ;
		CompassQueryBuilder builder = this.compassSession.queryBuilder();

		Date now = new Date() ;
		Date yesterday = DateTools.add(now, Calendar.DATE, -1) ;
		final CompassQuery dateRangeQuery = this.getDateRangeQuery("Post.date", yesterday, now, builder) ;
		CompassHits hits = builder.bool().addMust(dateRangeQuery).toQuery().hits();
		CompassHit[] hitArray = hits.detach().getHits() ;
		Statistics result = new Statistics(24) ;
		for (CompassHit hit: hitArray)
		{
			Post p = (Post)hit.data() ;
			int hour = DateTools.get(p.getDate(), Calendar.HOUR_OF_DAY) ;
			result.increment(hour) ;
		}
		transaction.commit();
		return result ;
	}
	/**
	 * @param memberId id of the user for which statistics is requested.
	 * @return a 30-day backwards statistics on post authoring.
	 */
	public Statistics getPostStatisticsForMember(final long memberId)
	{
		CompassTransaction transaction =  this.compassSession.beginTransaction() ;

		CompassQueryBuilder builder = this.compassSession.queryBuilder();
		Date today = new Date() ;
		Date lastMonth = DateTools.add(today, Calendar.MONTH, -1) ;
		final CompassQuery dateRangeQuery = this.getDateRangeQuery("Post.date", lastMonth, today, builder) ;
		CompassHits hits = builder.bool().addMust(dateRangeQuery).addMust(builder.queryString("writerId: " + memberId).toQuery()).toQuery().hits();
		CompassHit[] hitArray = hits.detach().getHits() ;
		Statistics result = new Statistics(32) ;
		for (CompassHit hit: hitArray)
		{
			Post p = (Post)hit.data() ;
			int day = DateTools.get(p.getDate(), Calendar.DATE) ;
			result.increment(day) ;
		}
		transaction.commit();
		return result ;
	}

	/**
	 * @return a per-hour user login statistics.
	 */
	public Statistics getUserStatistics()
	{
		CompassTransaction transaction =  this.compassSession.beginTransaction() ;
		CompassQueryBuilder builder = this.compassSession.queryBuilder();

		Date now = new Date() ;
		Date yesterday = DateTools.add(now, Calendar.DATE, -1) ;
		final CompassQuery dateRangeQuery = this.getDateRangeQuery("Login.time", yesterday, now, builder) ;
		CompassHits hits = builder.bool().addMust(dateRangeQuery).toQuery().hits();
		CompassHit[] hitArray = hits.detach().getHits() ;
		Statistics result = new Statistics(24) ;
		for (CompassHit hit: hitArray)
		{
			Login l = (Login)hit.data() ;
			int hour = DateTools.get(l.getTime(), Calendar.HOUR_OF_DAY) ;
			result.increment(hour) ;
		}
		transaction.commit();
		return result ;
	}
	/**
	 * @see domain.searchEngine.SearchEngine#searchByAuthor(java.lang.String)
	 */
	@Override
	public Set<SearchResult> searchByAuthor(final String authorName)
	{
		long authorId ;

		CompassTransaction transaction = null ;
		try
		{
			Member temp = this.forum.getUsers().getMember(authorName) ;
			if (temp == null)
			{
				throw new NotFoundException(authorName) ;
			}
			authorId = temp.getId() ;

			transaction =  this.compassSession.beginTransaction() ;

			CompassQueryBuilder builder = this.compassSession.queryBuilder();
			CompassHits hits = builder.bool().addMust(builder.queryString("Post.writerId: "+authorId).toQuery()).toQuery().hits();
			CompassHit[] hit = hits.detach(0, 100).getHits() ;
			Set<SearchResult> finalMessages = new HashSet<SearchResult>();
			for (CompassHit element: hit)
			{
				float score = element.score();
				Post p = (Post)(element.data());
				SearchResult search_r = new SearchResult(p,score);
				finalMessages.add(search_r);
			}
			transaction.commit();
			return finalMessages;
		}
		catch (NotFoundException e)
		{
			Log.getLogger(Subsystem.SEARCH_ENGINE).fine(e.getMessage()) ;
		}
		catch (Exception e)
		{
			Log.getLogger(Subsystem.SEARCH_ENGINE).fine(e.getMessage()) ;
			transaction.rollback();
		}
		return new HashSet<SearchResult>() ;
	}

	/**
	 * @see domain.searchEngine.SearchEngine#searchByContent(java.lang.String)
	 */
	@Override
	public Set<SearchResult> searchByContent(final String message)
	{

		CompassTransaction transaction =  this.compassSession.beginTransaction() ;

		try
		{
			CompassQueryBuilder builder = this.compassSession.queryBuilder();
			CompassHits hits = builder.bool().addMust(builder.queryString("Post.message:"+message).toQuery()).toQuery().hits();
			CompassHit[] hit = hits.detach(0, 100).getHits() ;
			Set<SearchResult> finalMessages = new HashSet<SearchResult>();
			for (CompassHit element: hit)
			{
				float score = element.score();
				Post p = (Post)(element.data());
				SearchResult search_r = new SearchResult(p,score);
				finalMessages.add(search_r);
			}
			transaction.commit();
			return finalMessages;
		}
		catch (Exception e)
		{
			Log.getLogger(Subsystem.SEARCH_ENGINE).fine(e.getMessage()) ;
			transaction.rollback();
			return new HashSet<SearchResult>();
		}

	}

	/**
	 * @see domain.searchEngine.SearchEngine#searchByDate(java.util.Date, java.util.Date)
	 */
	@Override
	public Set<SearchResult> searchByDate(final Date fromDate, final Date toDate)
	{
		CompassTransaction transaction =  this.compassSession.beginTransaction() ;
		try
		{
			CompassQueryBuilder builder = this.compassSession.queryBuilder();

			Set<SearchResult> finalMessages = new HashSet<SearchResult>();
			final CompassQuery dateRangeQuery = this.getDateRangeQuery("Post.date", fromDate, toDate, builder) ;
			CompassHits hits = builder.bool().addMust(dateRangeQuery).toQuery().hits();
			CompassHit[] hit = hits.detach(0, 100).getHits() ;
			for (CompassHit element: hit)
			{
				float score = element.score();
				Post p = (Post)(element.data());
				SearchResult search_r = new SearchResult(p,score);
				finalMessages.add(search_r);
			}
			transaction.commit();
			return finalMessages;
		}
		catch (Exception e)
		{
			Log.getLogger(Subsystem.SEARCH_ENGINE).fine(e.getMessage()) ;
			transaction.rollback();
			return new HashSet<SearchResult>();

		}

	}

	private CompassQuery getDateRangeQuery(final String property, final Date start, final Date end, final CompassQueryBuilder builder)
	{
		SimpleDateFormat formatter = new SimpleDateFormat(DateTools.GLOBAL_DATE_FORMAT);
		String startDateRange = formatter.format(start) ;
		String endDateRange = formatter.format(end) ;
		final CompassQuery dateRangeQuery = builder.queryString(property + ": [" + startDateRange + " TO " + endDateRange + "]").toQuery() ;
		return dateRangeQuery ;
	}

}
